package com.mas.admin.biz.service.oauth2;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.mas.admin.biz.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.mas.admin.biz.dal.dataobject.oauth2.OAuth2ClientDO;
import com.mas.admin.biz.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
import com.mas.admin.biz.dal.mysql.oauth2.OAuth2AccessTokenMapper;
import com.mas.admin.biz.dal.mysql.oauth2.OAuth2RefreshTokenMapper;
import com.mas.admin.biz.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import com.mas.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.mas.framework.common.util.date.DateUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.mas.framework.common.exception.util.ServiceExceptionUtil.exception0;

/**
 * @author godyao
 * @description OAuth2.0 Token Service 实现类
 * @date 2023年01月31日20:18
 */
@Service
@RequiredArgsConstructor
public class OAuth2TokenServiceImpl implements OAuth2TokenService {

    private final OAuth2ClientService oauth2ClientService;

    private final OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;

    private final OAuth2AccessTokenMapper oauth2AccessTokenMapper;

    private final OAuth2AccessTokenRedisDAO oAuth2AccessTokenRedisDAO;

    @Override
    public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) {
        // 检验 clientId 是否合法
        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
        // 创建 refreshToken
        OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes);
        // 创建访问令牌
        return createOAuth2AccessToken(refreshTokenDO, clientDO);
    }

    @Override
    public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
        if (Objects.isNull(accessTokenDO)) {
            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌不存在");
        }
        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌已过期");
        }
        return accessTokenDO;
    }

    @Override
    public OAuth2AccessTokenDO removeAccessToken(String accessToken) {
        // 删除mysql accessToken
        final OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
        if (Objects.isNull(accessTokenDO)) {
            return null;
        }
        oauth2AccessTokenMapper.deleteById(accessTokenDO);
        oAuth2AccessTokenRedisDAO.delete(accessToken);
        if (StrUtil.isNotBlank(accessTokenDO.getRefreshToken())) {
            // 删除 refreshToken
            oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken());
        }
        return accessTokenDO;
    }

    @Override
    public OAuth2AccessTokenDO refreshToken(String refreshToken, String clientId) {
        // 查询 refreshToken 信息
        final OAuth2RefreshTokenDO oAuth2RefreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
        if (Objects.isNull(oAuth2RefreshTokenDO)) {
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "无效的刷新令牌");
        }
        // 校验 Client
        final OAuth2ClientDO oAuth2ClientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
        if (ObjectUtil.notEqual(clientId, oAuth2RefreshTokenDO.getClientId())) {
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "刷新令牌的客户端编号不正确");
        }
        // 移除相关的 accessToken
        // 因为 refreshToken 的有效期比 accessToken 长，accessToken必然过期，删除即可
        List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken);
        if (CollUtil.isNotEmpty(accessTokenDOs)) {
            oauth2AccessTokenMapper.deleteBatchIds(accessTokenDOs.stream().map(OAuth2AccessTokenDO::getId).collect(Collectors.toSet()));
            oAuth2AccessTokenRedisDAO.deleteList0(accessTokenDOs);
        }
        // 已经过期的情况下，删除刷新令牌
        if (DateUtils.isExpired(oAuth2RefreshTokenDO.getExpiresTime())) {
            oauth2RefreshTokenMapper.deleteById(oAuth2RefreshTokenDO);
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "刷新令牌已过期");
        }
        // 创建访问令牌
        return createOAuth2AccessToken(oAuth2RefreshTokenDO, oAuth2ClientDO);
    }

    private OAuth2AccessTokenDO getAccessToken(String accessToken) {
        // 优先从 redis 获取
        OAuth2AccessTokenDO accessTokenDO = oAuth2AccessTokenRedisDAO.get(accessToken);
        if (Objects.nonNull(accessTokenDO)) return accessTokenDO;
        // redis 获取不到再从 mysql 获取
        accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
        // 如果在 MySQL 存在, 并且 token 没有过期，则往 Redis 中写入
        if (Objects.nonNull(accessTokenDO) && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
            oAuth2AccessTokenRedisDAO.set(accessTokenDO);
        }
        return accessTokenDO;
    }

    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
        final OAuth2AccessTokenDO oAuth2AccessTokenDO = new OAuth2AccessTokenDO()
                .setRefreshToken(refreshTokenDO.getRefreshToken())
                .setUserId(refreshTokenDO.getUserId())
                .setUserType(refreshTokenDO.getUserType())
                .setClientId(clientDO.getClientId())
                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()))
                .setAccessToken(IdUtil.fastSimpleUUID());
        oauth2AccessTokenMapper.insert(oAuth2AccessTokenDO);
        // 记录到 Redis 中
        oAuth2AccessTokenRedisDAO.set(oAuth2AccessTokenDO);
        return oAuth2AccessTokenDO;

    }


    /**
     * 新建 refreshToken
     * @author godyao
     * @param userId 用户编号
     * @param userType 用户类型
     * @param clientDO 客户端信息
     * @param scopes 作用域
     * @return com.mas.admin.biz.dal.dataobject.oauth2.OAuth2RefreshTokenDO refreshToken
     * @date 2023/1/31 21:05
     */
    private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes) {
        final OAuth2RefreshTokenDO oAuth2RefreshTokenDO = new OAuth2RefreshTokenDO()
                .setUserId(userId)
                .setUserType(userType)
                .setClientId(clientDO.getClientId())
                .setScopes(scopes)
                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds()))
                .setRefreshToken(IdUtil.fastSimpleUUID());
        oauth2RefreshTokenMapper.insert(oAuth2RefreshTokenDO);
        return oAuth2RefreshTokenDO;
    }
}
